<!DOCTYPE html>
<!--
Copyright (c) 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<script>
'use strict';

// Anonymous namespace
(function() {

// @return {Array<object>} List of events for an abbreviated URL request.
function urlRequestEvents(id) {
  return [
    {
      'phase': EventPhase.PHASE_BEGIN,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953534778',
      'type': EventType.REQUEST_ALIVE
    },
    {
      'params': {
        'load_flags': 68223104,
        'method': 'GET',
        'priority': 4,
        'url': 'http://www.google.com/'
      },
      'phase': EventPhase.PHASE_BEGIN,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953534792',
      'type': EventType.URL_REQUEST_START_JOB
    },
    {
      'phase': EventPhase.PHASE_END,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953534800',
      'type': EventType.URL_REQUEST_START_JOB
    },
    {
      'phase': EventPhase.PHASE_BEGIN,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953534906',
      'type': EventType.HTTP_TRANSACTION_SEND_REQUEST
    },
    {
      'params': {
        'headers': [
          'Host: www.google.com',
          'Connection: keep-alive',
          'User-Agent: Mozilla/5.0',
          'Accept: text/html',
          'Accept-Encoding: gzip,deflate',
          'Accept-Language: en-US,en;q=0.8',
          'Accept-Charset: ISO-8859-1',
          'X-Random-Header-With-Quotes: "Quoted String"""',
        ],
        'line': 'GET / HTTP/1.1\r\n'
      },
      'phase': EventPhase.PHASE_NONE,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953534910',
      'type': EventType.HTTP_TRANSACTION_SEND_REQUEST_HEADERS
    },
    {
      'phase': EventPhase.PHASE_END,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953534915',
      'type': EventType.HTTP_TRANSACTION_SEND_REQUEST
    },
    {
      'phase': EventPhase.PHASE_END,
      'source': {'id': id, 'type': EventSourceType.URL_REQUEST},
      'time': '953535567',
      'type': EventType.REQUEST_ALIVE
    }
  ];
}

tr.b.unittest.testSuite(function() {
  /**
   * Tests the filters, both in terms of filtering correctly and UI.
   */
  test('netInternalsEventsViewFilter', function() {
    this.addHTMLOutput(globalNetLogViewerElementForTests);
    MainView.getInstance();

    setNetLogConstantsForTest();

    // Sets the filter and checks the results.
    // @param {string} filter Filter to use.
    // @param {Array<boolean>} matches Ordered list of whether or not each
    //     source matches |filter|.  Order must match display order after
    //     applying the filter.
    function checkFilter(filter, matches) {
      EventsView.getInstance().setFilterText_(filter);

      var postFilter = 0;
      for (var i = 0; i < matches.length; ++i) {
        if (matches[i])
          ++postFilter;
      }

      // Updating the display is normally done asynchronously, so have to
      // manually call update function to check displayed event count.
      EventsView.getInstance().repaintFilterCounter_();

      chai.assert.strictEqual(
          postFilter + ' of ' + matches.length,
          $(EventsView.FILTER_COUNT_ID).innerText, filter);

      var tbody = $(EventsView.TBODY_ID);
      chai.assert.strictEqual(matches.length, tbody.childElementCount, filter);

      var visibleCount = 0;
      for (var i = 0; i < tbody.childElementCount; ++i) {
        chai.assert.strictEqual(
            matches[i], NetInternalsTest.nodeIsVisible(tbody.children[i]),
            filter);
      }
    }

    EventsTracker.getInstance().deleteAllLogEntries();
    checkFilter('', [], 0);

    // A completed request.
    g_browser.receivedLogEntries(urlRequestEvents(31));
    checkFilter('', [true], 1);

    // An incomplete request.
    g_browser.receivedLogEntries(urlRequestEvents(56).slice(0, 3));
    checkFilter('', [true, true], 2);

    // Filters used alone and in all combinations.
    // |text| is the string to add to the filter.
    // |matches| is a 2-element array of booleans indicating which of the two
    //      requests passes the filter.
    var testFilters = [
      {text: 'http://www.google.com', matches: [true, true]},
      {text: 'MyMagicPony', matches: [false, false]},
      {text: 'type:URL_REQUEST', matches: [true, true]},
      {text: 'type:SOCKET,URL_REQUEST', matches: [true, true]},
      {text: 'type:SOCKET', matches: [false, false]},
      {text: '-type:PONY', matches: [true, true]},
      {text: '-type:URL_REQUEST', matches: [false, false]},
      {text: 'id:31,32', matches: [true, false]},
      {text: '-id:31,32', matches: [false, true]},
      {text: 'id:32,56,', matches: [false, true]},
      // Searching by text should also match on source ID.
      {text: '56', matches: [false, true]},
      {text: '-is:active', matches: [true, false]},
      {text: 'is:active', matches: [false, true]},
      {text: '-is:error', matches: [true, true]},
      {text: 'is:error', matches: [false, false]},
      // Partial match of source type.
      {text: 'URL_REQ', matches: [true, true]},
      // Partial match of event type type.
      {text: 'SEND_REQUEST', matches: [true, false]},
      // Check that ":" works in strings.
      {text: 'Host:', matches: [true, false]},
      {text: '::', matches: [false, false]},
      // Test quotes.
      {text: '"Quoted String"', matches: [true, false]},
      {text: '"Quoted source"', matches: [false, false]},
      {text: '"\\"Quoted String\\""', matches: [true, false]},
      {text: '"\\"\\"Quoted String\\""', matches: [false, false]},
      {text: '\\"\\"\\"', matches: [true, false]},
      {text: '\\"\\"\\"\\"', matches: [false, false]},
      {
        text: '"Host: www.google.com"',
        matches: [true, false],
      },
      {
        text: 'Connection:" keep-alive"',
        matches: [true, false],
      },
      {
        text: '-Connection:" keep-alive"',
        matches: [false, true],
      },
      {text: '"Host: GET"', matches: [false, false]},
      // Make sure sorting has no effect on filters.  Sort by ID so order is
      // preserved.
      {text: 'sort:"id"', matches: [true, true]},
      {text: '-sort:"shoe size"', matches: [true, true]},
      {text: '"-sort:shoe size"', matches: [false, false]},
    ];

    for (var filter1 = 0; filter1 < testFilters.length; ++filter1) {
      checkFilter(testFilters[filter1].text, testFilters[filter1].matches);
      // Check |filter1| in combination with all the other filters.
      for (var filter2 = 0; filter2 < testFilters.length; ++filter2) {
        var matches = [];
        for (var i = 0; i < testFilters[filter1].matches.length; ++i) {
          // The merged filter matches an entry if both individual filters do.
          matches[i] =
              testFilters[filter1].matches[i] && testFilters[filter2].matches[i];
        }

        checkFilter(
            testFilters[filter1].text + ' ' + testFilters[filter2].text, matches,
            1);
      }
    }

    // Tests with unmatched quotes.  Unlike the strings above, combining them
    // with other filters is not the same as applying both filters
    // independently.
    checkFilter('"Quoted String', [true, false]);
    checkFilter('"Quoted String source', [false, false]);
    checkFilter('Quoted" String', [true, false]);
    checkFilter('Quoted" source', [false, false]);
    checkFilter('Quoted "String', [true, false]);

    // Test toggling sort method, without any filters.
    var eventsView = EventsView.getInstance();
    eventsView.setFilterText_('');
    $(EventsView.SORT_BY_DESCRIPTION_ID).click();
    chai.assert.strictEqual('sort:desc', eventsView.getFilterText_());
    $(EventsView.SORT_BY_DESCRIPTION_ID).click();
    chai.assert.strictEqual('-sort:desc', eventsView.getFilterText_());
    $(EventsView.SORT_BY_ID_ID).click();
    chai.assert.strictEqual('sort:id', eventsView.getFilterText_());

    // Sort by default is by ID, so toggling ID when there's no filter results
    // in -sort:id.
    eventsView.setFilterText_('');
    $(EventsView.SORT_BY_ID_ID).click();
    chai.assert.strictEqual('-sort:id', eventsView.getFilterText_());
    $(EventsView.SORT_BY_ID_ID).click();
    chai.assert.strictEqual('sort:id', eventsView.getFilterText_());

    // Test toggling sort method with filters.
    eventsView.setFilterText_('text');
    $(EventsView.SORT_BY_ID_ID).click();
    chai.assert.strictEqual('-sort:id text', eventsView.getFilterText_());
    $(EventsView.SORT_BY_ID_ID).click();
    chai.assert.strictEqual('sort:id text', eventsView.getFilterText_());
    $(EventsView.SORT_BY_SOURCE_TYPE_ID).click();
    chai.assert.strictEqual('sort:source text', eventsView.getFilterText_());
    eventsView.setFilterText_('text sort:id "more text"');
    $(EventsView.SORT_BY_ID_ID).click();
    chai.assert.strictEqual('-sort:id text "more text"',
                            eventsView.getFilterText_());

    unsetNetLogConstantsForTest();
  });
});
})();  // Anonymous namespace
</script>
